package com.lahuan.base;

import com.lahuan.demo.entity.Order;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.sql.DataSource;
import java.io.*;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.text.SimpleDateFormat;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;


@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = MillionInsert.class)
@SpringBootApplication(scanBasePackages = "com.lahuan")
public class MillionInsert {

    @Autowired
    DataSource dataSource;
    @Autowired
    JdbcTemplate jdbcTemplate;
    //待付款:wait 已付款:pay 已完成:finish 已取消:cancel 已无效:invalid
    private final String[] statusArray ={"wait","pay","finish","cancel","invalid"};
    /**
     * 时间戳 2020-01-01 00:00:00
     */
    long timeStart = 1577808000000L;
    /**
     * 时间戳 2021-12-31 23:59:59
     */
    long end =   1640966399000L;
    /**
     * 满多少条记录就提交
     */
    int commitCount = 10000;
    /**
     * 一天
     */
    long day = 1000 * 60 * 60 * 24 ;
    /**
     * 总数据大小
     */
    int total = 1_000_000;
    private BufferedWriter out = null;
    private void initFile() throws Exception {
        // 文件名
        String filePath = "E:\\test\\week7_load_data_test_million.csv";
        Path path = Paths.get(filePath);
        if(!Files.exists(path)){
            Files.createFile(path);
        }
        out = new BufferedWriter(new OutputStreamWriter(
                new FileOutputStream(new File(filePath), true)));
    }
    public synchronized void appendFile(String content) throws IOException {
        out.write(content);
        out.flush();
    }
    @Test
    public void buildLoadData() throws Exception {
        initFile();
        long startCreatObj = System.currentTimeMillis();
        Order[] data = buildDate();
        long costCreatObj  = System.currentTimeMillis() - startCreatObj;
        log.info("创建对象完成,耗时:{}ms",costCreatObj);

        int cores = Runtime.getRuntime().availableProcessors()  ;
        Thread[] threads = new Thread[cores];
        int perSize = total/cores;
        AtomicInteger left =new AtomicInteger(total);
        long startInsert = System.currentTimeMillis();
        for (int i = 0; i < cores; i++) {
            int start = i * perSize;
            if(i == cores -1){
                threads[i] = new Thread(()->{
                    try {
                        runBuildDataToFile(start,left.get(),data);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
            }else{
                left.addAndGet(-perSize);
                threads[i] = new Thread(()->{
                    try {
                        runBuildDataToFile(start,perSize,data);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
            }
            threads[i].setName("thread:"+i);
            threads[i].start();
        }

        for (int i = 0; i < cores; i++) {
            threads[i].join();
        }


        long costInsert  = System.currentTimeMillis() - startInsert;

        log.info("多线程创建文件完成,核心数:{},数量:{},耗时:{}ms",cores,total,costInsert);

    }

    @Test
    public void testManyThreadBatch() throws Exception {
        truncate();
        long startCreatObj = System.currentTimeMillis();
        Order[] data = buildDate();
        long costCreatObj  = System.currentTimeMillis() - startCreatObj;
        log.info("创建对象完成,耗时:{}ms",costCreatObj);

        int cores = Runtime.getRuntime().availableProcessors()  ;
        Thread[] threads = new Thread[cores];
        int perSize = total/cores;
        AtomicInteger left =new AtomicInteger(total);
        long startInsert = System.currentTimeMillis();
        for (int i = 0; i < cores; i++) {
            int start = i * perSize;
            if(i == cores -1){
                threads[i] = new Thread(()->{
                    try {
                        runInsertBatch(start,left.get(),data);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
            }else{
                left.addAndGet(-perSize);
                threads[i] = new Thread(()->{
                    try {
                        runInsertBatch(start,perSize,data);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
            }
            threads[i].setName("thread:"+i);
            threads[i].start();
        }

        for (int i = 0; i < cores; i++) {
            threads[i].join();
        }


        long costInsert  = System.currentTimeMillis() - startInsert;

        log.info("多线程插入完成,核心数:{},数量:{},耗时:{}ms",cores,total,costInsert);
    }
    @Test
    public void testManyThread() throws Exception {
        truncate();
        long startCreatObj = System.currentTimeMillis();
        Order[] data = buildDate();
        long costCreatObj  = System.currentTimeMillis() - startCreatObj;
        log.info("创建对象完成,耗时:{}ms",costCreatObj);

        int cores = Runtime.getRuntime().availableProcessors();
        Thread[] threads = new Thread[cores];
        int perSize = total/cores;
        AtomicInteger left =new AtomicInteger(total);
        long startInsert = System.currentTimeMillis();
        for (int i = 0; i < cores; i++) {
            int start = i * perSize;
            if(i == cores -1){
                threads[i] = new Thread(()->{
                    try {
                        runInsert(start,left.get(),data);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
            }else{
                left.addAndGet(-perSize);
                threads[i] = new Thread(()->{
                    try {
                        runInsert(start,perSize,data);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
            }
            threads[i].setName("thread:"+i);
            threads[i].start();
        }

        for (int i = 0; i < cores; i++) {
            threads[i].join();
        }


        long costInsert  = System.currentTimeMillis() - startInsert;

        log.info("多线程插入完成,核心数:{},数量:{},耗时:{}ms",cores,total,costInsert);
    }
    @Test
    public void testOneThread() throws Exception {
        truncate();
        long startCreatObj = System.currentTimeMillis();
        Order[] data = buildDate();
        long costCreatObj  = System.currentTimeMillis() - startCreatObj;
        log.info("创建对象完成,耗时:{}ms",costCreatObj);
        long startInsert = System.currentTimeMillis();
        runInsert(0,total,data);
        long costInsert  = System.currentTimeMillis() - startInsert;
        log.info("单线程插入完成,数量:{},耗时:{}ms",total,costInsert);
    }


    private Order buildSingleData(int no){
        Random rd = new Random();
        Order order = new Order();
        order.setOrderSn("no_"+no);
        //金额 1-999
        order.setTotalAmount(new BigDecimal(rd.nextInt(999)+1));
        //随机状态
        order.setOrderStatus(statusArray[rd.nextInt(statusArray.length)]);
        order.setIsPay(rd.nextInt(2));
        //2020-01-01 为起点，两年内的随机一天
        order.setPayTime(new Date(timeStart +( rd.nextInt(365 ) * day) ));
        order.setUserId((long) (rd.nextInt(100)+1));
        return order;
    }
    private void truncate(){
        jdbcTemplate.execute("truncate table t_order;");
    }
    private Order[] buildDate(){
        Order[] result = new Order[total];
        for (int i = 0; i < total; i++) {
            result[i] = buildSingleData(i);
        }
        return result;
    }
    private  void runInsertBatch(int start, int len, Order[] data ) throws Exception {
        Connection connection = dataSource.getConnection();
        connection.setAutoCommit(false);
        //"INSERT INTO `t_order` ( `user_id`, `order_sn`, `total_amount`, `order_status`, `is_pay`, `pay_time`)VALUES\t( ?, ?, ?, ?, ?, ? );" ;
        String insert = "INSERT INTO `t_order` ( `user_id`, `order_sn`, `total_amount`, `order_status`, `is_pay`, `pay_time`)VALUES\t( ?, ?, ?, ?, ?, ? )";
        PreparedStatement preparedStatement = connection.prepareStatement(insert);
        int count = 0;
        int total = 0;
        int maxRun  = start +len;
        for (int i = start; i < maxRun; i++) {

            Order o =  data[i];
            preparedStatement.setLong(1, o.getUserId());
            preparedStatement.setString(2, o.getOrderSn());
            preparedStatement.setBigDecimal(3, o.getTotalAmount());
            preparedStatement.setString(4, o.getOrderStatus());
            preparedStatement.setInt(5,o.getIsPay());
            preparedStatement.setDate(6, o.getPayTime());
            preparedStatement.addBatch();
            count ++ ;
            total ++;
            if(count >= commitCount){
                preparedStatement.executeBatch();
                connection.commit();
                count = 0;
                log.info("{}:插入了:{}条",Thread.currentThread().getName(),total);
            }
        }
        if(count >0){
            preparedStatement.executeBatch();
            connection.commit();
            log.info("{}:循环结束,插入了:{}条",Thread.currentThread().getName(),total);
        }
        connection.close();
    }
    private  void runInsert(int start, int len, Order[] data ) throws Exception {
        Connection connection = dataSource.getConnection();
        connection.setAutoCommit(false);
        //"INSERT INTO `t_order` ( `user_id`, `order_sn`, `total_amount`, `order_status`, `is_pay`, `pay_time`)VALUES\t( ?, ?, ?, ?, ?, ? );" ;
        String insert = "INSERT INTO `t_order` ( `user_id`, `order_sn`, `total_amount`, `order_status`, `is_pay`, `pay_time`)VALUES\t( ?, ?, ?, ?, ?, ? );";
        PreparedStatement preparedStatement = connection.prepareStatement(insert);
        int count = 0;
        int total = 0;
        int maxRun  = start +len;
        for (int i = start; i < maxRun; i++) {

            Order o =  data[i];
            preparedStatement.setLong(1, o.getUserId());
            preparedStatement.setString(2, o.getOrderSn());
            preparedStatement.setBigDecimal(3, o.getTotalAmount());
            preparedStatement.setString(4, o.getOrderStatus());
            preparedStatement.setInt(5,o.getIsPay());
            preparedStatement.setDate(6, o.getPayTime());
            preparedStatement.execute();
            count ++ ;
            total ++;
            if(count >= commitCount){
                connection.commit();
                count = 0;
                log.info("{}:插入了:{}条",Thread.currentThread().getName(),total);
            }
        }
        if(count >0){
            connection.commit();
            log.info("{}:循环结束,插入了:{}条",Thread.currentThread().getName(),total);
        }
        connection.close();
    }

    private  void runBuildDataToFile(int start, int len, Order[] data ) throws Exception {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        int count = 0;
        int total = 0;
        int maxRun  = start +len;
        StringBuilder sb = new StringBuilder();
        for (int i = start; i < maxRun; i++) {
            Order o =  data[i];
            sb.append(o.getUserId()).append(",");
            sb.append(o.getOrderSn()).append(",");
            sb.append(o.getTotalAmount()).append(",");
            sb.append(o.getOrderStatus()).append(",");
            sb.append(o.getIsPay()).append(",");
            sb.append( sdf.format(o.getPayTime())).append("\r\n");
            count++;
            total ++;
            if(count >= commitCount){
                appendFile(sb.toString());
                count = 0;
                log.info("{}:创建了:{}条",Thread.currentThread().getName(),total);
                sb = new StringBuilder();
            }
        }

        if(count >0){
            appendFile(sb.toString());
            log.info("{}:循环结束,创建了:{}条",Thread.currentThread().getName(),total);
        }

    }
}
